﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.Mirror.CliMetadata.Tables;
using System.Runtime.InteropServices;

namespace Kokomo.Mirror.CliMetadata
{
	class CliType : CliImplementedType
	{
		public CliType(
			TypeDefRow typeDefRow,
			CliMemberBasicInfo basicInfo
			)
			: base(basicInfo)
		{
			//if (typeDefRow == null) throw new ArgumentNullException("typeDefRow");

			this._typeDefRow = typeDefRow;
			this._baseType = new Lazy<Type>(this.LoadBaseType);
			this._guid = new Lazy<Guid>(this.DetermineGuid);

			this._classLayoutRow = new Lazy<ClassLayoutRow>(this.LoadClassLayoutRow);
			//this._genericParams = new Lazy<Type[]>(this.LoadGenericParams);
			this._genericParams = this.LoadGenericParams();
		}

		protected override TypeCode GetTypeCodeImpl()
		{
			return MirrorLoadContext.GetTypeCode(this);
		}

		private TypeDefRow _typeDefRow;
		internal override TypeDefRow TypeDefRow
		{
			get { return this._typeDefRow; }
		}

		private Lazy<Type> _baseType;
		private Type LoadBaseType()
		{
			if (this._typeDefRow.Extends.RowNumber == 0) return null;
			Type baseType = this.Module.ResolveType(this._typeDefRow.Extends.Token, this.GetGenericArguments(), null);
			return baseType;
		}
		public override Type BaseType
		{
			get { return this._baseType.Value; }
		}

		protected override TypeAttributes GetAttributeFlagsImpl()
		{
			return this._typeDefRow.Flags;
		}

		#region Attributes
		private Guid DetermineGuid()
		{
			IList<CustomAttributeData> attributeData = this.GetCustomAttributesData();
			Guid guid = new Guid();
			foreach (var attribute in attributeData)
			{
				if (attribute.Constructor.ReflectedType == typeof(GuidAttribute))
				{
					string guidText = (string)attribute.ConstructorArguments[0].Value;
					guid = new Guid(guidText);
					break;
				}
			}

			return guid;
		}
		private Lazy<Guid> _guid;
		public override Guid GUID
		{
			get { return this._guid.Value; }
		}
		#endregion

		#region Generics
		//private Lazy<Type[]> _genericParams;
		private Type[] _genericParams;
		private Type[] LoadGenericParams()
		{
			return this.basicInfo.module.QueryGenericParams(this, this);
		}

		public override Type[] GetGenericArguments()
		{
			//return this._genericParams.Value;
			return this._genericParams;
			//return this.LoadGenericParams();
		}
		public override bool ContainsGenericParameters
		{
			get
			{
				//return this._genericParams.Value.Length > 0;
				return this.GetGenericArguments().Length > 0;
			}
		}
		public override bool IsGenericType
		{
			get { return this.IsGenericTypeDefinition; }
		}
		public override bool IsGenericTypeDefinition
		{
			get { return this.ContainsGenericParameters; }
		}
		private void ValidateGenericArgument(
			Type argumentType,
			Type parameterType
			)
		{
			if ((object)argumentType == null) throw new ArgumentNullException("argumentType");
			if ((object)parameterType == null) throw new ArgumentNullException("parameterType");

			GenericParameterAttributes paramAttributes = parameterType.GenericParameterAttributes;

			//bool isInvalid =
			//    (paramAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) && !argumentType.IsClass)
			//    || (paramAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint | System.Reflection.GenericParameterAttributes.ReferenceTypeConstraint) && (argumentType.GetConstructor(Type.EmptyTypes) == null))
			//    || (paramAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) && !argumentType.IsValueType)
			//    ;
			//if (isInvalid)
			//    throw new ArgumentException("Invalid generic type argument");
		}
		public override Type MakeGenericType(params Type[] typeArguments)
		{
			Type[] genericParams = this.GetGenericArguments();
			if (typeArguments.Length != genericParams.Length)
				throw new ArgumentException("Incorrect number of type arguments");

			for (int i = 0; i < typeArguments.Length; i++)
			{
				this.ValidateGenericArgument(typeArguments[i], genericParams[i]);
			}

			CliGenericType genericType = new CliGenericType(
				this.basicInfo,
				this,
				typeArguments
				);

			return genericType;
		}
		public override Type GetGenericTypeDefinition()
		{
			if (!this.IsGenericTypeDefinition)
				throw new InvalidOperationException();

			return this;
		}
		#endregion

		public override string Namespace
		{
			get { return this.basicInfo.module.Metadata.GetString(this._typeDefRow.TypeNamespace); }
		}

		public override string Name
		{
			get { return this.basicInfo.module.Metadata.GetString(this._typeDefRow.TypeName); }
		}

		private Lazy<ClassLayoutRow> _classLayoutRow;
		private ClassLayoutRow LoadClassLayoutRow()
		{
			return this.basicInfo.module.QueryClassLayout(this.MetadataToken);
		}

		public override StructLayoutAttribute StructLayoutAttribute
		{
			get
			{
				TypeAttributes attributes = this.Attributes;
				LayoutKind layoutKind = LayoutKind.Auto;
				if (attributes.HasFlag(TypeAttributes.SequentialLayout))
					layoutKind = LayoutKind.Sequential;
				else if (attributes.HasFlag(TypeAttributes.ExplicitLayout))
					layoutKind = LayoutKind.Explicit;

				CharSet charset = CharSet.None;
				if (attributes.HasFlag(TypeAttributes.UnicodeClass))
					charset = CharSet.Unicode;
				else if (attributes.HasFlag(TypeAttributes.AnsiClass))
					charset = CharSet.Ansi;

				TypeAttributes stringFormatAttributes = this.Attributes & TypeAttributes.StringFormatMask;
				ClassLayoutRow layoutRow = this._classLayoutRow.Value;

				StructLayoutAttribute layoutAttribute = new StructLayoutAttribute(layoutKind)
				{
					CharSet = charset
				};
				if (layoutRow != null)
				{
					layoutAttribute.Pack = layoutRow.PackingSize;
					layoutAttribute.Size = layoutRow.ClassSize;
				}

				return layoutAttribute;
			}
		}
		#region Enum
		public override Array GetEnumValues()
		{
			if (!this.IsEnum)
				throw new NotSupportedException();

			string[] names = Enum.GetNames(this);
			Array values = Array.CreateInstance(this.GetEnumUnderlyingType(), names.Length);
			for (int i = 0; i < names.Length; i++)
			{
				FieldInfo field = this.GetField(names[i], BindingFlags.Static | BindingFlags.Public);
				object value = field.GetRawConstantValue();
				values.SetValue(value, i);
			}

			return values;
		}
		#endregion

		public override Type Reflect()
		{
			Type reflected;
			if (this.DeclaringType != null)
			{
				Type declaring = this.DeclaringType.Reflect();
				reflected = declaring.GetNestedType(this.Name);
			}
			else
			{
				Assembly assembly = this.Assembly.Reflect();
				reflected = assembly.GetType(this.FullName);
			}

			return reflected;
		}
	}
}
